/*
 * Copyright 2011 Noctarius (Christoph Engelbert)
 * Part of www.sourceprojects.org 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may 
 * not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *    http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.sourceprojects.cappadocia;

import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;

class ClassInfoImpl implements ClassInfo, AnnotatedElement {
	private static final Logger LOG = Logger.getLogger(ClassInfoImpl.class);

	private final ClassPool classPool;

	private final Collection<AnnotationInfo> annotations = new HashSet<AnnotationInfo>();
	private final Collection<MethodInfo> methods = new HashSet<MethodInfo>();
	private final Collection<ClassInfo> interfaces = new HashSet<ClassInfo>();
	private ClassInfo superclass;
	private String canonicalName;
	private String name;
	private boolean annotationType;
	private boolean enumType;
	private boolean arrayType;
	private int modifiers;
	private boolean synthetic;
	private boolean anonymous;
	private boolean local;

	private final Set<String> interfaceClasses = new HashSet<String>();
	private String superclassClass;

	private final Lock interfaceLock = new ReentrantLock();
	private final Lock superclassLock = new ReentrantLock();

	ClassInfoImpl(final ClassPool classPool) {
		this.classPool = classPool;
	}

	@Override
	public Collection<AnnotationInfo> getAnnotations() {
		return Collections.unmodifiableCollection(annotations);
	}

	@Override
	public String getCanonicalName() {
		return canonicalName;
	}

	@Override
	public Collection<ClassInfo> getInterfaces() {
		interfaceLock.lock();
		try {
			checkInterfacesResolving();
			return Collections.unmodifiableCollection(interfaces);

		} finally {
			interfaceLock.unlock();
		}
	}

	@Override
	public Collection<MethodInfo> getMethods() {
		return Collections.unmodifiableCollection(methods);
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public ClassInfo getSuperclass() {
		superclassLock.lock();
		try {
			checkSuperclassResolving();
			return superclass;

		} finally {
			superclassLock.unlock();
		}
	}

	@Override
	public boolean isAnnotation() {
		return annotationType;
	}

	@Override
	public boolean isAnonymous() {
		return anonymous;
	}

	@Override
	public boolean isArray() {
		return arrayType;
	}

	@Override
	public boolean isEnum() {
		return enumType;
	}

	@Override
	public boolean isLocal() {
		return local;
	}

	@Override
	public boolean isPackagePrivate() {
		return !isPrivate() && !isPublic() && !isProtected();
	}

	@Override
	public boolean isProtected() {
		return Modifier.isProtected(modifiers);
	}

	@Override
	public boolean isPrivate() {
		return Modifier.isPrivate(modifiers);
	}

	@Override
	public boolean isPublic() {
		return Modifier.isPublic(modifiers);
	}

	@Override
	public boolean isStatic() {
		return Modifier.isStatic(modifiers);
	}

	@Override
	public boolean isSynthetic() {
		return synthetic;
	}

	@Override
	public boolean isFinal() {
		return Modifier.isFinal(modifiers);
	}

	@Override
	public boolean isAbstract() {
		return Modifier.isAbstract(modifiers);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (annotationType ? 1231 : 1237);
		result = prime * result
				+ ((annotations == null) ? 0 : annotations.hashCode());
		result = prime * result + (anonymous ? 1231 : 1237);
		result = prime * result + (arrayType ? 1231 : 1237);
		result = prime * result
				+ ((canonicalName == null) ? 0 : canonicalName.hashCode());
		result = prime * result
				+ ((classPool == null) ? 0 : classPool.hashCode());
		result = prime * result + (enumType ? 1231 : 1237);
		result = prime * result
				+ ((interfaces == null) ? 0 : interfaces.hashCode());
		result = prime * result + (local ? 1231 : 1237);
		result = prime * result + ((methods == null) ? 0 : methods.hashCode());
		result = prime * result + modifiers;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result
				+ ((superclass == null) ? 0 : superclass.hashCode());
		result = prime * result + (synthetic ? 1231 : 1237);
		return result;
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final ClassInfoImpl other = (ClassInfoImpl) obj;
		if (annotationType != other.annotationType) {
			return false;
		}
		if (annotations == null) {
			if (other.annotations != null) {
				return false;
			}
		} else if (!annotations.equals(other.annotations)) {
			return false;
		}
		if (anonymous != other.anonymous) {
			return false;
		}
		if (arrayType != other.arrayType) {
			return false;
		}
		if (canonicalName == null) {
			if (other.canonicalName != null) {
				return false;
			}
		} else if (!canonicalName.equals(other.canonicalName)) {
			return false;
		}
		if (classPool == null) {
			if (other.classPool != null) {
				return false;
			}
		} else if (!classPool.equals(other.classPool)) {
			return false;
		}
		if (enumType != other.enumType) {
			return false;
		}
		if (interfaces == null) {
			if (other.interfaces != null) {
				return false;
			}
		} else if (!interfaces.equals(other.interfaces)) {
			return false;
		}
		if (local != other.local) {
			return false;
		}
		if (methods == null) {
			if (other.methods != null) {
				return false;
			}
		} else if (!methods.equals(other.methods)) {
			return false;
		}
		if (modifiers != other.modifiers) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		if (superclass == null) {
			if (other.superclass != null) {
				return false;
			}
		} else if (!superclass.equals(other.superclass)) {
			return false;
		}
		if (synthetic != other.synthetic) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		final StringBuilder builder = new StringBuilder();
		builder.append("ClassInfoImpl [annotationType=").append(annotationType)
				.append(", annotations=").append(annotations)
				.append(", anonymous=").append(anonymous)
				.append(", arrayType=").append(arrayType)
				.append(", canonicalName=").append(canonicalName)
				.append(", classPool=").append("ClassPool@")
				.append(classPool.hashCode()).append(", enumType=")
				.append(enumType).append(", interfaces=").append(interfaces)
				.append(", local=").append(local).append(", methods=")
				.append(methods).append(", modifiers=").append(modifiers)
				.append(", name=").append(name).append(", superclass=")
				.append(superclass).append(", synthetic=").append(synthetic)
				.append("]");
		return builder.toString();
	}

	void setSuperclass(final String superclassClass) {
		this.superclassClass = superclassClass;
	}

	void setCanonicalName(final String canonicalName) {
		this.canonicalName = canonicalName;
	}

	void setName(final String name) {
		this.name = name;
	}

	void setAnnotationType(final boolean annotationType) {
		this.annotationType = annotationType;
	}

	void setEnumType(final boolean enumType) {
		this.enumType = enumType;
	}

	void setArrayType(final boolean arrayType) {
		this.arrayType = arrayType;
	}

	void setModifiers(final int modifiers) {
		this.modifiers = modifiers;
	}

	void setSynthetic(final boolean synthetic) {
		this.synthetic = synthetic;
	}

	void setAnonymous(final boolean anonymous) {
		this.anonymous = anonymous;
	}

	void setLocal(final boolean local) {
		this.local = local;
	}

	@Override
	public void addAnnotation(final AnnotationInfo annotationInfo) {
		annotations.add(annotationInfo);
	}

	Collection<MethodInfo> getMethods0() {
		return methods;
	}

	Collection<String> getInterfaces0() {
		return interfaceClasses;
	}

	void checkInterfacesResolving() {
		if (interfaces.size() == 0 && interfaceClasses.size() > 0) {
			for (final String canonicalName : interfaceClasses) {
				final ClassInfo info = classPool.resolveClass(canonicalName);

				if (info != null) {
					interfaces.add(info);
				} else {
					LOG.warn("Interface '" + canonicalName
							+ "' could not be resolved. Please "
							+ "ensure that it is in search classpool.");
				}
			}
			interfaceClasses.clear();
		}
	}

	void checkSuperclassResolving() {
		if (superclass == null && superclassClass != null) {
			final ClassInfo info = classPool.resolveClass(superclassClass);

			if (info != null) {
				superclass = info;
				superclassClass = null;
			}
		}
	}

}
